package com.gss.stockLedger.service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import org.springframework.beans.BeanUtils;

import com.gss.stockLedger.dao.QueryStockNoDaoSupportDAO;
import com.gss.stockLedger.domain.FIFOReportDTO;
import com.gss.stockLedger.domain.StockTransactionDtls;
import com.gss.stockLedger.form.ReportGenerateForm;

public class FifoDataProcessorThread implements Runnable {
	
	private final GenerateExcelThread generateExcelThread;
	private final Object[] partIdDetails;
	private final boolean negativePartAnalysis;
	private final List<FIFOReportDTO> negativePartList;
	private final QueryStockNoDaoSupportDAO queryStockNoDaoSupportDAO;
	private final ReportGenerateForm reportForm;
	private final ThreadCounter threadCounter;
	
	public FifoDataProcessorThread(GenerateExcelThread excelThread,Object[] partIdDetails,boolean negativePartAnalysis,
			List<FIFOReportDTO> negativePartList,QueryStockNoDaoSupportDAO dao,ReportGenerateForm reportGenerateForm,
			ThreadCounter threadCounter){
		this.generateExcelThread = excelThread;
		this.partIdDetails = partIdDetails;
		this.negativePartAnalysis = negativePartAnalysis;
		this.negativePartList = negativePartList;
		this.queryStockNoDaoSupportDAO = dao;
		this.reportForm = reportGenerateForm;
		this.threadCounter = threadCounter;
	}

	@Override
	public void run() {
		
		List<FIFOReportDTO> finalReportList = new ArrayList<FIFOReportDTO>();

		List<StockTransactionDtls> stockTransactionDtls = null;
		try {
			stockTransactionDtls = queryStockNoDaoSupportDAO.getStockTransactionsForProduct(reportForm, partIdDetails[0].toString(), partIdDetails[1].toString());
			} catch (Exception e) {
			e.printStackTrace();
			this.threadCounter.setNumberOfThreads(-1);
			return;
		}
		//logger.debug("number of stock entries to process: " + stockTransactionDtls.size()+ " ----- Processing " + ++partIdCounter + " of " + totalPartIds );
		if(stockTransactionDtls.size() == 0){
			this.threadCounter.setNumberOfThreads(-1);
			return;
		}
		
		//sorted list according to receipt date
		
		LinkedList<FIFOReportDTO> transactionQ = null;
		Stack<FIFOReportDTO> consumedStack = null;
		LinkedList<FIFOReportDTO> transactionQForReport = null;
		Date previousTransactDate = null;
		Calendar lastTransactionDate = Calendar.getInstance();
		Calendar currentTransactionDate = Calendar.getInstance();
		long prevClosingQuantity = 0;

		transactionQ = new LinkedList<FIFOReportDTO>();
		transactionQForReport = new LinkedList<FIFOReportDTO>();
		consumedStack = new Stack<FIFOReportDTO>();
		
		long activeQuantity=0;
		float activeRate=0;
		double activeValue=0.0;
		float currentRate = 0.0F;
		FIFOReportDTO activeRow ;
		
		// in the following loop, all receipts are accumulated into a linked list so that they can be later used for consumption following FIFO
		try{
		for (StockTransactionDtls stockTransactionDtls2 : stockTransactionDtls) {
			activeRow = new FIFOReportDTO();
			BeanUtils.copyProperties(stockTransactionDtls2, activeRow);
			
				if(activeRow.getTransactionType().equalsIgnoreCase("ZGRIR")) {
					//change code to incorporate multiple grir entries on same day with same assignment number
					if(transactionQ.size()>0&&transactionQ.contains(activeRow))
					{
						int index =transactionQ.indexOf(activeRow);
						FIFOReportDTO fromQueue = transactionQ.get(index);
						fromQueue.setQuantity(fromQueue.getQuantity()+activeRow.getQuantity());
						fromQueue.setValue(fromQueue.getValue() + activeRow.getQuantity()*activeRow.getRate());
// commented by Mayank
//							if(fromQueue.getQuantity()<=0)
//								transactionQ.remove(index);
					}
					else
					{
						FIFOReportDTO temp = new FIFOReportDTO();
						BeanUtils.copyProperties(activeRow, temp);
						/*if(null == temp.getQuantity())
							temp.setQuantity((long)0);*/
						temp.setValue((double) (temp.getQuantity() * temp.getRate()));
						transactionQ.add(temp);
						
					}
				}
			}
	
	// applying all adjustments on respective Purchases (GRIR)
	
		for (StockTransactionDtls stockTransactionDtls2 : stockTransactionDtls) {
			activeRow = new FIFOReportDTO();
			BeanUtils.copyProperties(stockTransactionDtls2, activeRow);
			
			if(activeRow.getTransactionType().equalsIgnoreCase("CUSTOM") || activeRow.getTransactionType().equalsIgnoreCase("COSTPRICE") || 
					activeRow.getTransactionType().equalsIgnoreCase("RETRO") || activeRow.getTransactionType().equalsIgnoreCase("FOREX")
					||activeRow.getTransactionType().equalsIgnoreCase("INVADJ")) {
				
				for (FIFOReportDTO fifoReportDTO : transactionQ) {
					if(null != activeRow.getAssignmentId() && activeRow.getAssignmentId().equalsIgnoreCase(fifoReportDTO.getAssignmentId())){
						if(activeRow.getTransactionType().equalsIgnoreCase("INVADJ")){
							if(activeRow.getQuantity()!=null)
							fifoReportDTO.setQuantity(fifoReportDTO.getQuantity()+activeRow.getQuantity());
						}
						if(activeRow.getValue()!=null)
							fifoReportDTO.setValue(fifoReportDTO.getValue() + activeRow.getValue());
						
						if(fifoReportDTO.getQuantity() != 0)
							fifoReportDTO.setRate((float) (fifoReportDTO.getValue() / fifoReportDTO.getQuantity()));
						else
							fifoReportDTO.setRate(0.0f);
						
						break;
					}
				}
			}
		}
	
		// above loop results into a transactionQ with all receipts along with their reversals and adjustments
		// running loop on this transactionQ, to remove any entries with zero or negative quantity or value
		List<FIFOReportDTO> toRemoveList = new ArrayList<FIFOReportDTO>();
		for(FIFOReportDTO receipts : transactionQ){
			if(receipts.getQuantity() <=0 || receipts.getValue() <=0){
				//transactionQ.remove(transactionQ.indexOf(receipts));
//				System.err.println("GRIR which does not qualify for consumption after all adjustments: " + receipts.getAssignmentId());
				//indexToRemoveList.add(transactionQ.indexOf(receipts));
				toRemoveList.add(receipts);
			}else {
				receipts.setRate((float) (receipts.getValue() / receipts.getQuantity()));
			}
		}
		if(toRemoveList.size() > 0){
			for(FIFOReportDTO index : toRemoveList)
				transactionQ.remove(index);
		}
		
		// now we have transactionQ with all entries having positive Quantity and positive value and rate populated based on final values
	
		long counter = -1;
		FIFOReportDTO previousRow = null;
		
		lastTransactionDate.setTime(stockTransactionDtls.get(0).getTransactionDate());
		prevClosingQuantity = 0;

		for (StockTransactionDtls stockTransactionDtls2 : stockTransactionDtls) {
			
			
			activeRow = new FIFOReportDTO();
			BeanUtils.copyProperties(stockTransactionDtls2, activeRow);
			counter++;
			if(counter==0)
			{
				
				previousTransactDate = activeRow.getTransactionDate();
				if(activeRow.getTransactionDate().after(reportForm.getSecStartDate())){
					finalReportList.add(activeRow);
				}
				
				if(activeRow.getTransactionType().equalsIgnoreCase("ZGRIR"))
				{
					FIFOReportDTO temp = new FIFOReportDTO();
					BeanUtils.copyProperties(activeRow, temp);
					if(transactionQ.size()>0 && transactionQ.contains(temp))
					{
						temp.setRate(transactionQ.getFirst().getRate());
						temp.setValue(transactionQ.getFirst().getValue());
						temp.setQuantity(transactionQ.getFirst().getQuantity());
						transactionQForReport.add(temp);
						transactionQ.removeFirst();
					}
				}
				else if(activeRow.getTransactionType().equalsIgnoreCase("OPENING")){
					FIFOReportDTO temp = new FIFOReportDTO();
					BeanUtils.copyProperties(activeRow, temp);
					temp.setRate((float) (temp.getValue() / temp.getQuantity()));
					temp.setAssignmentId("OpeningGRN");
					transactionQForReport.add(temp);
					
					//change to show rate as value/quantity
					activeRow.setRate((float) (temp.getValue() / temp.getQuantity()));
					//transactionQ.removeFirst();
				}else if(FifoReportGenerationThreadedService.Consumption_Transaction_Type.contains(activeRow.getTransactionType())){
					
					FIFOReportDTO consumedStockDTO = new FIFOReportDTO();
					consumedStockDTO.setTransactionType("ZGRIR");
					consumedStockDTO.setAssignmentId("ApplicationGenerated");
					consumedStockDTO.setQuantity(activeRow.getQuantity());
					consumedStockDTO.setValue(0.0d);
					consumedStockDTO.setRate(currentRate);
					consumedStack.push(consumedStockDTO);
					
					activeRow.setQuantity(activeRow.getQuantity() * -1);
					activeRow.setRate(0.0f);
					activeRow.setValue(0.0d);
				}
				
				activeRow.setClosingQuantity(activeRow.getQuantity()!=null?activeRow.getQuantity():0);
				activeRow.setClosingRate(activeRow.getRate()!=null?activeRow.getRate():0);
				activeRow.setClosingValue(activeRow.getValue()!=null?activeRow.getValue():0);
				activeQuantity =activeRow.getQuantity()!=null?activeRow.getQuantity():0;
				if(activeRow.getValue()!=null)
					activeValue = activeRow.getValue();
				if(activeRow.getQuantity()!= null && activeRow.getQuantity() > 0)
					activeRate = (float) (activeValue / activeQuantity) ;
				else
					activeRate = 0.0f;

				lastTransactionDate.setTime(activeRow.getTransactionDate());
				prevClosingQuantity = activeRow.getClosingQuantity();
				continue;
			}
			
			String transactionType = stockTransactionDtls2.getTransactionType();
			
			if(transactionType.equalsIgnoreCase("ZGRIR")) //grir is purchase 
			{
				FIFOReportDTO temp = new FIFOReportDTO();
				BeanUtils.copyProperties(activeRow, temp);
				if(transactionQ.size()>0 && transactionQ.contains(temp))
				{
					temp.setRate(transactionQ.getFirst().getRate());
					temp.setValue(transactionQ.getFirst().getValue());
					temp.setQuantity(transactionQ.getFirst().getQuantity());
					transactionQForReport.add(temp);
					transactionQ.removeFirst();
				}
				
				activeRow.setClosingQuantity(activeQuantity+=activeRow.getQuantity());
				activeRow.setClosingValue(activeRow.getValue()!=null?activeValue+=activeRow.getValue():activeValue);
				activeRow.setClosingRate((float) (activeValue/activeQuantity));
			}
			else if(transactionType.equalsIgnoreCase("CPA"))
			{
				
			}
			else if(FifoReportGenerationThreadedService.Consumption_Transaction_Type.contains(transactionType) && activeRow.getQuantity() > 0){
				
				long consumedQuantity = 0;
				long quantityToConsume = activeRow.getQuantity();
				double consumedQuantityValue = 0.0;

				while(quantityToConsume>0)
				{
					if(transactionQForReport!=null&&transactionQForReport.size()>0)
					{
						FIFOReportDTO purchaseToConsume = transactionQForReport.getFirst();
					
						if(quantityToConsume>purchaseToConsume.getQuantity())
						{
							quantityToConsume-=purchaseToConsume.getQuantity();
							consumedQuantity+=purchaseToConsume.getQuantity();
							consumedQuantityValue+=(purchaseToConsume.getQuantity()*purchaseToConsume.getRate());
							transactionQForReport.removeFirst();
							currentRate = purchaseToConsume.getRate();
							
							if((!consumedStack.isEmpty())&&consumedStack.peek().equals(purchaseToConsume)){
								consumedStack.peek().setQuantity((long) (purchaseToConsume.getValue() / purchaseToConsume.getRate()));
							}else{
								FIFOReportDTO temp = new FIFOReportDTO();
								BeanUtils.copyProperties(purchaseToConsume, temp);
								consumedStack.push(temp);
							}
							
							
						}
						else
						{
							purchaseToConsume.setQuantity(purchaseToConsume.getQuantity()-quantityToConsume);
							consumedQuantity += quantityToConsume;
							consumedQuantityValue +=quantityToConsume*purchaseToConsume.getRate();
							quantityToConsume =0;
							
							if(!consumedStack.empty()&&consumedStack.peek().equals(purchaseToConsume)){
								consumedStack.peek().setQuantity((long) (purchaseToConsume.getValue() / purchaseToConsume.getRate()) - purchaseToConsume.getQuantity());
							}else{
								FIFOReportDTO temp = new FIFOReportDTO();
								BeanUtils.copyProperties(purchaseToConsume, temp);
								temp.setQuantity((long) (purchaseToConsume.getValue() / purchaseToConsume.getRate()) - purchaseToConsume.getQuantity());
								consumedStack.push(temp);
							}
						}
					}
					else
					{
						FIFOReportDTO consumedStockDTO = new FIFOReportDTO();
						consumedStockDTO.setTransactionType("ZGRIR");
						consumedStockDTO.setAssignmentId("ApplicationGenerated");
						consumedStockDTO.setQuantity(quantityToConsume);
						if(currentRate != 0)
							consumedStockDTO.setValue((double) (quantityToConsume*currentRate));
						else
							consumedStockDTO.setValue(0.0d);
						consumedStockDTO.setRate(currentRate);
						consumedStack.push(consumedStockDTO);
						
						consumedQuantity+=quantityToConsume;
						consumedQuantityValue += quantityToConsume*currentRate;
						quantityToConsume=0;
						
					}
				}
				activeRow.setClosingQuantity(activeQuantity-=consumedQuantity); 
				activeRow.setClosingValue(activeValue-=consumedQuantityValue);
				activeRow.setClosingRate((float) (activeValue/activeQuantity));
				
				activeRow.setRate((float) (consumedQuantityValue/consumedQuantity));
				activeRow.setQuantity(activeRow.getQuantity() * -1);
				activeRow.setValue(consumedQuantityValue * -1);
			}
			else if(FifoReportGenerationThreadedService.Consumption_Transaction_Type.contains(transactionType) && activeRow.getQuantity() < 0){
				long quantityToReverse = activeRow.getQuantity() * -1l;
				long reversedQuantity = 0;
				double reversedQuantityValue = 0.0d;
				
				if(consumedStack.empty())
				{
					reversedQuantity = quantityToReverse;
					reversedQuantityValue = 0;
				}else{
					while(quantityToReverse > 0&&!consumedStack.empty()){
						FIFOReportDTO consumptionEntry = consumedStack.peek();
						if(consumptionEntry.getQuantity()>quantityToReverse){
							consumptionEntry.setQuantity(consumptionEntry.getQuantity() - quantityToReverse);
							reversedQuantityValue+= quantityToReverse * consumptionEntry.getRate();
							if(!consumptionEntry.getAssignmentId().equalsIgnoreCase("ApplicationGenerated")){
								if(transactionQForReport.size() > 0 && transactionQForReport.getFirst().equals(consumptionEntry)){
									transactionQForReport.getFirst().setQuantity(transactionQForReport.getFirst().getQuantity() + quantityToReverse);
								}else{
									FIFOReportDTO temp = new FIFOReportDTO();
									BeanUtils.copyProperties(consumptionEntry, temp);
									temp.setQuantity(quantityToReverse);
									transactionQForReport.add(0,temp);
								}
							}
							reversedQuantity+=quantityToReverse;
							quantityToReverse-=quantityToReverse;
						}else{
							
							if(!consumptionEntry.getAssignmentId().equalsIgnoreCase("ApplicationGenerated")){
								if(transactionQForReport.size()>0&&transactionQForReport.getFirst().equals(consumptionEntry)){
									transactionQForReport.getFirst().setQuantity(transactionQForReport.getFirst().getQuantity() + consumptionEntry.getQuantity());
								}else{
									FIFOReportDTO temp = new FIFOReportDTO();
									BeanUtils.copyProperties(consumptionEntry, temp);
									//temp.setQuantity(quantityToReverse);
									transactionQForReport.add(0,temp);
								}
							}
							reversedQuantityValue+=consumptionEntry.getQuantity()*consumptionEntry.getRate();
							reversedQuantity+=consumptionEntry.getQuantity();
							quantityToReverse-=consumptionEntry.getQuantity();
							consumedStack.pop();
						}
					}
					if(quantityToReverse > 0){
						reversedQuantity +=quantityToReverse;
						quantityToReverse = 0;
					}
				}
					
					
			activeRow.setClosingQuantity(activeQuantity+=reversedQuantity); 
			activeRow.setClosingValue(activeValue+=reversedQuantityValue);
			activeRow.setClosingRate((float) (activeValue/activeQuantity));
			
			activeRow.setRate((float) (reversedQuantityValue/reversedQuantity));
			activeRow.setQuantity(activeRow.getQuantity() * -1);
			activeRow.setValue(reversedQuantityValue);
			
			}
			else if(activeRow.getTransactionType().equalsIgnoreCase("CUSTOM") || activeRow.getTransactionType().equalsIgnoreCase("COSTPRICE") || 
					activeRow.getTransactionType().equalsIgnoreCase("RETRO") || activeRow.getTransactionType().equalsIgnoreCase("FOREX")||
					activeRow.getTransactionType().equalsIgnoreCase("INVADJ")) {
				if(activeRow.getTransactionType().equalsIgnoreCase("INVADJ")){
					if(activeRow.getQuantity()!=null)
						activeRow.setClosingQuantity(activeQuantity+=activeRow.getQuantity());
					else
						activeRow.setClosingQuantity(activeQuantity);
				}
				else
					activeRow.setClosingQuantity(activeQuantity);
				
				if(activeRow.getValue()!=null)
					activeRow.setClosingValue(activeValue+=activeRow.getValue());
				else
					activeRow.setClosingValue(activeValue);//to be changed
				
				activeRow.setClosingRate((float) (activeValue / activeQuantity));
				if(!activeRow.getTransactionType().equalsIgnoreCase("INVADJ"))
					activeRow.setQuantity(null);
				activeRow.setRate(null);
			}
			
			if(activeRow.getTransactionDate().after(reportForm.getSecStartDate())){
				finalReportList.add(activeRow);
			}
			
			if(finalReportList.size() == 1 && null != previousRow ){
				finalReportList.add(finalReportList.get(0));
				previousRow.setTransactionType("OPENING");
				previousRow.setQuantity(null);
				previousRow.setRate(null);
				previousRow.setValue(null);
				previousRow.setTransactionDate(reportForm.getStartDate());
				finalReportList.set(0, previousRow);
			}
			previousRow = activeRow;
			
			currentTransactionDate.setTime(stockTransactionDtls2.getTransactionDate());
			if(negativePartAnalysis && lastTransactionDate.get(Calendar.DATE) != currentTransactionDate.get(Calendar.DATE)
					 && prevClosingQuantity < 0){
				
				System.out.println("##################### DATE CHANGED #####################");
				System.out.println("Part Id: " +activeRow.getPartId() + " Closing Quantity: " + activeRow.getClosingQuantity() + " Inv Type: " + activeRow.getInventoryType()
						 + ":: " + currentTransactionDate.get(Calendar.DATE) + "////" + lastTransactionDate.get(Calendar.DATE));
				negativePartList.add(activeRow);
				System.out.println("SIZE OF NEGATIVE PART LIST: " + negativePartList.size());
				break;
			}
			
			lastTransactionDate.setTime(stockTransactionDtls2.getTransactionDate());
			prevClosingQuantity = activeRow.getClosingQuantity();
		}
		
		
		//add data in thread map
		if(!negativePartAnalysis){
			generateExcelThread.getPartId_data_map().put(partIdDetails[0].toString()+ "####" + partIdDetails[1].toString(), finalReportList);
//			System.out.println("size of map: " + generateExcelThread.getPartId_data_map().size());
		}
		}catch (Throwable e) {
			e.printStackTrace();
		}finally{
			this.threadCounter.setNumberOfThreads(-1);
		}
		
	}

}
